using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using PropertyExpression.Common;

namespace PropertyExpression.ControllableQuery.Test
{
    public static class QueryContextExtensions
    {
        public static IParam<Decimal<TPrecision, TScale>> CreateDecimalParam<TPrecision, TScale>(this SqlCommand it)
            where TPrecision : IPrecision where TScale : IScale
        {
            return it.CreateParam<Decimal<TPrecision, TScale>>(
                name => new SqlParameter(name, SqlDbType.Decimal)
                            {
                                Precision = Decimal<TPrecision, TScale>.Precision,
                                Scale = Decimal<TPrecision, TScale>.Scale
                            },
                _ => _.Value);
        }

        public static IParam<NVarChar<TLength>> CreateNVarCharParam<TLength>(this SqlCommand it) 
            where TLength : ILength
        {
            return it.CreateParam<NVarChar<TLength>>(GetStringSqlParameterFunc(), _ => _.Value);
        }

        private static Func<string, SqlParameter> GetStringSqlParameterFunc()
        {
            return name => new SqlParameter(name, SqlDbType.NVarChar) { Size = -1 };
        }

        public static IParam<IOption<NVarChar<TLength>>> CreateOptionalNVarCharParam<TLength>(this SqlCommand it)
            where TLength : ILength
        {
            return it.CreateParam<IOption<NVarChar<TLength>>>(
                GetStringSqlParameterFunc(),
                _ => _.Some(nVarChar => nVarChar.Value).GetValueOrDbNull());
        }

        public static IParam<Money> CreateMoneyParam(this SqlCommand it)
        {
            return it.CreateParam<Money>(name => new SqlParameter(name, SqlDbType.Money), _ => _.Value);
        }

        public static IParam<ITableValued<T>> CreateTableValuedParam<T>(this SqlCommand it)
        {
            return it.CreateParam<ITableValued<T>>(
                parameterName => new SqlParameter(parameterName, SqlDbType.Structured)
                                     {
                                         TypeName = typeof (T).Name
                                     },
                _ => ConvertToDataTable(_.Value)
                );
        }

        public static IParam<IOption<string>> Param(this SqlCommand it, IOption<string> value)
        {
            var param = it.CreateOptionalStringParam();
            param.Value = value;
            return param;
        }

        public static IParam<Decimal<TPrecision, TScale>> Param<TPrecision, TScale>(this SqlCommand it, Decimal<TPrecision, TScale> value)
            where TPrecision : IPrecision where TScale : IScale
        {
            var param = it.CreateDecimalParam<TPrecision, TScale>();
            param.Value = value;
            return param;
        }

        public static IParam<NVarChar<TLength>> Param<TLength>(this SqlCommand it, NVarChar<TLength> value)
            where TLength : ILength
        {
            var param = it.CreateNVarCharParam<TLength>();
            param.Value = value;
            return param;
        }

        public static IParam<IOption<NVarChar<TLength>>> Param<TLength>(this SqlCommand it, IOption<NVarChar<TLength>> value)
            where TLength : ILength
        {
            var param = it.CreateOptionalNVarCharParam<TLength>();
            param.Value = value;
            return param;
        }

        public static IParam<Money> Param(this SqlCommand it, Money value)
        {
            var param = it.CreateMoneyParam();
            param.Value = value;
            return param;
        }

        public static IParam<IOption<int>> Param(this SqlCommand it, IOption<int> value)
        {
            var param = it.CreateOptionalIntParam();
            param.Value = value;
            return param;
        }

        public static IParam<ITableValued<T>> Param<T>(this SqlCommand it, ITableValued<T> value)
        {
            var param = it.CreateTableValuedParam<T>();
            param.Value = value;
            return param;
        }

        private static DataTable ConvertToDataTable<T>(IEnumerable<T> value)
        {
            var dataTable = new DataTable();
            var propertyInfos = typeof(T).GetProperties();
            foreach (var propertyInfo in propertyInfos)
            {
                dataTable.Columns.Add(propertyInfo.Name, propertyInfo.PropertyType);
            }
            foreach (var item in value)
            {
                foreach (var propertyInfo in propertyInfos)
                {
                    var dataRow = dataTable.NewRow();
                    dataRow[propertyInfo.Name] = propertyInfo.GetValue(item, null);
                    dataTable.Rows.Add(dataRow);
                }
            }
            return dataTable;
        }
    }
}